#ifndef __tenacitas_log_core__file__h__
#define __tenacitas_log_core__file__h__


/** @file
    In this file is implemented tenacitas::log::core::file template class */

/** @todo  */

// ==> C++ Headers
#include <string>
#include <cstdint>
#include <sstream>
#include <fstream>
#include <memory>
#include <chrono>
#include <cstdio>
#include <utility>
#include <type_traits>

// ==> 3rds Headers

// ==> Our Headers
#include "level.h"
#include "thread_id.h"

#include <tenacitas.return_code.core/rc.h>

// ==> Namespaces
using namespace std;

// ==> Macro Constants

// ==> Macro Commands


/** namespace of the organization */
namespace tenacitas {

    /** ==> namespace of the project */
    namespace log {

        namespace core {
            

            // ==> Typedefs 

            // ==> Pre-Declarations

            // ==> Attributes 

            // ==> Functions
        
            // ==> Classes
        
            /** @p file implements a @p media for tenacitas::log::logger class

                The media is, as expected, a file. However, it is not decided if a
                text or binary file; the format of the messages; how is the size
                of the file managed, how the (possible) generated files are
                merged, etc. These decisions are left to other classes,
                implemented as type parameters. 

                @tparam supplier provides the types needed for @p file to work,
                listed below. 



                @tparam supplier::writer is responsible for actually writing the
                log messages to a file. It must implement:

                <tt>void rc<std::ofstream>open_file(const std::string &
                p_file_name) noexcept </tt>, which opens the file @p p_file_name

                <tt>rc<> close_file( ofstream & p_file) noexcept</tt>, which
                closes the file @p p_file 

                <tt>rc<> new_line(ofstream & p_file,
                                  uint64_t p_timestamp,
                                  tenacitas::log::thread_id p_thread_id,
                                  tenacitas::log::level p_level,
                                  const char * p_file_name,
                                  int64_t p_line) noexcept</tt>, 
                which starts a new line in the log file. @p p_file is the file
                where a new log line should be recorded; @p p_level is the level
                of the log message; @p p_file_name is the name of the file where
                the log message is being generated and @p p_line is the line
                number in @p p_file_name where the log message is being
                generated;   

                <tt>template<typename data>
                rc<> write(ofstream & p_file, const data & p_data) noexcept</tt>,
                which writes @p p_data to @p p_file 

                <tt>template<typename data>
                rc<>  write (ofstream & p_file, data && p_data) noexcept</tt>,
                which writes @p p_data to @p p_file 




                @tparam supplier::change_file_notifier is a policy that defines
                how/when logging should continue in another file. It must
                implement: 

                <tt>rc<bool> change_now(std::uint64_t p_size)</tt>, which informs
                if a new file should be created. To help the decision, the
                current file size is passed.




                @tparam supplier::change_file_handler is responsible for handling
                the event of changing the file where the log messages should be
                written to. It must implement:

                <tt>change_file_handler(thread_id p_th_id)</tt>

                <tt>rc<> file_closed() noexcept</tt> which is called when the
                file where the log messages are being written to is closed

                <tt>rc<> file_changed() noexcept</tt> which is called when the
                file where the log messages are being written needs to be changed 

                <tt>rc<std::string> file_name() noexcept</tt> which retrieves
                the name of the current file 



                <b>A word to the programmer user</b>
                You, dear programer, only need to worry about this class if you
                want to write your own @p file specialization.
                If you just want to use a media based on a file, you can check,
                for instance, on the tenacitas::log::text_file class.
            */
            template <typename supplier>
            class file {
            public:
                // ==> file - Public Internal Types

                enum constants : constant_id {
                    error_telling_file_pos = 0
                        };
                
                
                // ==> file - Friends

                /** */
                friend class file_tester;
                
                // ==> file - Public Constructors

                /** Destructor */
                ~file();

                /** Constructor */
                file(const thread_id & p_thread_id);
            
                /** not allowed */
                file(const file &) = delete;

                /**  */
                file(file && p_file);

                // ==> file - Public Processors

                /** Writes data to the file

                    Writes any data type that to the file, according to the
                    @p writer class

                    @param  p_data is the data to be written to the file
                */
                template <typename data>
                rc<> write (const data & p_data) noexcept;

                /** Writes data to the file

                    Writes any data type that to the file, according to the
                    @p writer class

                    @param  p_data is the data to be written to the file
                */
                template <typename data>
                rc<> write (data && p_data) noexcept;
                
                /** Starts a new line in the file

                    This method is called by the tenacitas::log::logger object
                    where this @p file object is defined

                    @param p_thread_id is the identifier of the thread that is
                    starting a new log line
                    @param  p_file is the file where a new log line should be
                    recorded;
                    @param  p_level is the level of the log message;
                    @param  p_line is the line number in @p p_file_name where the
                    log message is being generated
                */
                rc<> new_line(thread_id p_thread_id,
                              uint64_t p_timestamp,
                              level p_level,
                              const char * p_file,
                              int64_t p_line) noexcept;
            
                // ==> file - Public Operators
            
                /** not-allowed */
                file & operator = (const file &) = delete;

                /** */
                file & operator = (file && p_file);

                // ==> file - Public Attributes

                /** */
                static const class_id id = 14346581951790ULL;

            private:

                // ==> file - Private Internal Types

                /** */
                typedef typename supplier::writer writer;

                /** */
                typedef typename supplier::change_file_notifier
                change_file_notifier;

                /** */
                typedef typename supplier::change_file_handler
                change_file_handler;
                
                // ==> file - Private Constructors

                // ==> file - Private Attributes

                /** Stream where the log messages will be written to*/
                std::unique_ptr<std::ofstream> m_file;

                /** It will notify when the log file should be changed */
                change_file_notifier m_change_file_notifier;

                /** It will write log messages into the file */
                writer m_writer;

                /** It will actually do something when the file needs to be
                    changed */  
                change_file_handler m_change_file_handler;

                /** Current file size */
                uint64_t m_file_size;
            };
        }
    }
}

// =============================================================================
//                     I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::log::core;

// -----------------------------------------------------------------------------
template <typename supplier>
const class_id
file<supplier>::id;


// -----------------------------------------------------------------------------
template <typename supplier>
file<supplier>::
file(const thread_id & p_thread_id)
    : m_file(),
      m_change_file_notifier(),
      m_writer(),
      m_change_file_handler(p_thread_id), 
      m_file_size (0) {

    rc<std::string> _rc1(m_change_file_handler.file_name());
    if (!_rc1) {
        throw _rc1;
    }
    
    rc<std::unique_ptr<std::ofstream>> _rc2(m_writer.open_file(_rc1.get_obj())); 
    if (!_rc2) {
        throw _rc2;
    }

    m_file = std::unique_ptr<std::ofstream>(std::move(_rc2.get_obj()));

    try {
        m_file_size = m_file->tellp();
    } catch (std::exception & _ex) {
        throw (rc<>(id, constants::error_telling_file_pos, __FILE__, __LINE__,
                    _ex.what()));
    }
}


// -----------------------------------------------------------------------------
template <typename supplier>
file<supplier>::
~file() {
    if (m_file) {

        rc<> _rc(m_writer.close_file(*m_file));
        if (!_rc) {
            std::cerr << _rc << std::endl;
        }
    
        _rc = std::move(m_change_file_handler.file_closed());
        if (!_rc) {
            std::cerr << _rc << std::endl;
        }
    }
}

// -----------------------------------------------------------------------------
template <typename supplier>
file<supplier>::
file(file && p_file)
    : m_file(std::move(p_file.m_file)),
      m_change_file_notifier(std::move(p_file.m_change_file_notifier)),
      m_writer(std::move(p_file.m_writer)),
      m_change_file_handler(std::move(p_file.m_change_file_handler)),
      m_file_size(std::move(p_file.m_file_size)) {}
    

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename data>
rc<>
file<supplier>::
write (const data & p_data) noexcept {
    rc<> _rc (m_writer.write(*m_file, p_data));

    if (_rc) {
        m_file_size += (static_cast<uint64_t>(m_file->tellp()) - m_file_size);
    }
    return _rc;
}

// -----------------------------------------------------------------------------
template <typename supplier>
template <typename data>
rc<>
file<supplier>::
write (data && p_data) noexcept {
    rc<> _rc (m_writer.write(*m_file, p_data));

    if (_rc) {
        m_file_size += (static_cast<uint64_t>(m_file->tellp()) - m_file_size);
    }
    return _rc;
}

// -----------------------------------------------------------------------------
template <typename supplier>
rc<>
file<supplier>::
new_line(thread_id p_thread_id,
         uint64_t p_timestamp,
         level p_level,
         const char * p_file,
         int64_t p_line) noexcept {
    rc<> _rc (m_change_file_notifier->change_now(m_file_size));
    if (!_rc) { return _rc;}
    
    _rc = std::move(m_writer.close_file(*m_file));
    if (_rc) { return _rc; }
    
    _rc = std::move(m_change_file_handler.file_changed());
    if (!_rc) {return _rc;}
    
    _rc = std::move(m_writer.open_file(*m_file,
                                        m_change_file_handler.file_name()));
    if (!_rc) {return _rc;}
                
    _rc = std::move(m_writer.new_line(*m_file, p_timestamp, p_thread_id, p_level,
                                       p_file, p_line));
    if (!_rc) {return _rc;}

    auto _tellp = m_file->tellp();
    if (!m_file->good()) {
        _rc = rc<>(id, constants::error_telling_file_pos, __FILE__, __LINE__);
        return _rc;
    }
    
        
    m_file_size += (static_cast<uint64_t>(_tellp) - m_file_size);

    return rc<>();
    
}


// -----------------------------------------------------------------------------
template <typename supplier>
file<supplier> &
file<supplier>::
operator = (file && p_file) {
    if (this != & p_file) {        
        m_file = std::move(p_file.m_file);
        m_change_file_notifier = std::move(p_file.m_change_file_notifier);
        m_writer = std::move(p_file.m_writer);
        m_change_file_handler = std::move(p_file.m_change_file_handler); 
        m_file_size  = std::move(p_file.file_size);
    }
    return *this;
    
}



#endif


